package com.hzit.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzit.common.utils.MyDate;
import com.hzit.seckill.dao.SeckillGoodsDao;
import com.hzit.seckill.entity.TbSeckillGoods;
import com.hzit.seckill.service.SeckillService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@Service
@EnableScheduling
@EnableAsync
public class SeckillServiceImpl implements SeckillService {

	@Autowired
	private SeckillGoodsDao goodsDao;
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private RedissonClient redissonClient;
	
	private static final String SECKILL_GOODS_PREFIX = "seckillGoods_";
	private static final String SECKILL_LOCK_PREFIX = "seckill:lock";
	//1. 每隔30s加载一次数据
	@Override
	@Async
	@Scheduled(cron = "*/30 * * * * ?")
	public void loadData() {
		//1.0 加锁
		RLock lock = redissonClient.getLock(SECKILL_LOCK_PREFIX);
		try {
			lock.lock(10,TimeUnit.SECONDS);

			//1.1 获取从当前时间算起的5个时间段
			List<Date> dateMenus = MyDate.getDateMenus();
			//1.2 遍历这五个时间段
			for (Date dateMenu : dateMenus) {
				//1.2.1 查询秒杀商品
				QueryWrapper<TbSeckillGoods> wrapper = new QueryWrapper<>();
				wrapper.ge("start_time",dateMenu)                               // 开始时间大于秒杀开始的时间
						.lt("end_time",MyDate.addDateHour(dateMenu,2))     // 结束时间小于秒杀开始时间加2
						.gt("stock_count",0)                                // 库存量大于0
						.eq("status",1);                                    // 审核通过的商品才可参与秒杀

				//1.2.2 得到redis中存放的秒杀商品的key的集合
				BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SECKILL_GOODS_PREFIX + MyDate.getDateStr(dateMenu));
				Set<String> keys = operations.keys();
				if(keys != null && keys.size() > 0){
					wrapper.notIn("id",keys);
				}
				//1.3 根据条件查询出秒杀商品
				List<TbSeckillGoods> seckillGoodsList = goodsDao.selectList(wrapper);

				//1.4 将商品放到redis中
				for (TbSeckillGoods tbSeckillGoods : seckillGoodsList) {
					// BoundHashOperations<String, String, String> seckillOperations = redisTemplate.boundHashOps("seckillGoods_" + MyDate.getDateStr(dateMenu));
					Boolean hasKey = redisTemplate.hasKey("seckillGoods_" + tbSeckillGoods.getId());
					if(!hasKey){
						operations.put(tbSeckillGoods.getId() + "", JSON.toJSONString(tbSeckillGoods));
					}
					//1. 为了避免重复超卖，可以定义一个数组，这个数组的长度就是商品库存量，数组的元素内容可以任意，这们这里放的是商品id
					String[] ids = new String[tbSeckillGoods.getStockCount()];
					for (int i = 0; i < ids.length; i++) {
						ids[i] = tbSeckillGoods.getId() + "";
					}
					//todo...可以增加幂等性判断
					redisTemplate.boundListOps("SeckillGoodsCountList_" + tbSeckillGoods.getId()).leftPushAll(ids);
					//2. 为了精准控制，可以将商品的库存量放到redis中，每次购买时对此库存量值减-1，再重新设置回库存中
					redisTemplate.boundHashOps("SeckillGoodsCount").put(tbSeckillGoods.getId() + "",tbSeckillGoods.getStockCount() + "");

				}
				// 设置存放商品的到期时间
				operations.expire( MyDate.addDateHour(dateMenu,2).getTime() - new Date().getTime(), TimeUnit.MILLISECONDS );
			}




		}finally {
			// 释放锁
			lock.unlock();
		}
	}
	//2. 从redis中取得数据
	@Override
	public List<TbSeckillGoods> getDataFromRedis() {
		List<TbSeckillGoods> seckillGoodsList = new ArrayList<>();
		//2.1 得到当前秒杀时间段
		List<Date> dateMenus = MyDate.getDateMenus();
		Date date = dateMenus.get(0);
		BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SECKILL_GOODS_PREFIX + MyDate.getDateStr(date));

		//2.2 将集合中的字符串转换为TbSeckillGoods 并放到新集合中
		List<String> values = operations.values();
		for (String value : values) {
			seckillGoodsList.add(JSON.parseObject(value,TbSeckillGoods.class));
		}

		//或者
		// List<TbSeckillGoods> collect = operations.values().stream().map(m -> {
		// 	TbSeckillGoods tbSeckillGoods = JSON.parseObject(m, TbSeckillGoods.class);
		// 	return tbSeckillGoods;
		// }).collect(Collectors.toList());
		return seckillGoodsList;
	}

	//3. 根据商品id查询商品对象
	@Override
	public TbSeckillGoods findById(String id) {
		Date date = MyDate.getDateMenus().get(0);
		BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SECKILL_GOODS_PREFIX + MyDate.getDateStr(date));
		String s = operations.get(id);
		return JSON.parseObject(s,TbSeckillGoods.class);
	}
}
